import requests
from io import BytesIO
from PIL import Image
import numpy as np
from pathlib import Path
import pickle
from collections import defaultdict
import networkx as nx
import cv2


重叠度阈值 = 0.3
偏离阈值 = 0.3


def 字转码(字):
    return f'u{ord(字):04x}'


def 获取二值图像(字):
    路径 = Path(f'二值图像/{字}')
    if Path.exists(路径):
        with open(路径, 'rb') as 文件:
            return pickle.load(文件)

    码 = 字转码(字)
    print('码:{码}')
    url = f'https://zhs.glyphwiki.org/glyph/{码}.png'
    应答 = requests.get(url)

    img = cv2.imdecode(np.frombuffer(
        应答.content, dtype=np.uint8), flags=cv2.IMREAD_GRAYSCALE)
    _, 二值图像 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
    二值图像 = cv2.bitwise_not(二值图像)

    目录 = 路径.parent
    if not 目录.exists():
        目录.mkdir(parents=True, exist_ok=True)

    with open(路径, 'wb') as 文件:
        pickle.dump(二值图像, 文件)
    return 二值图像


def 获取连通域表(字):
    二值图像 = 获取二值图像(字)
    核 = np.ones((3, 3), np.uint8)
    二值图像 = cv2.erode(二值图像, 核, 2)
    行数 = len(二值图像)
    列数 = len(二值图像[0])
    标记表表, 连通域数 = connected_components(np.array(二值图像), connectivity=8)
    数量, 左, 右, 顶, 底 = tuple(range(5))
    连通域典 = {标记: [0, 列数, 0, 行数, 0] for 标记 in range(1, 连通域数+1)}
    总数量 = 0
    for 行索引 in range(行数):
        for 列索引 in range(列数):
            标记 = 标记表表[行索引][列索引]
            if 标记 > 0:
                总数量 += 1
                连通域典[标记][数量] += 1
                连通域典[标记][左] = min(连通域典[标记][左], 列索引)
                连通域典[标记][右] = max(连通域典[标记][右], 列索引)
                连通域典[标记][顶] = min(连通域典[标记][顶], 行索引)
                连通域典[标记][底] = max(连通域典[标记][底], 行索引)

    数量阈值 = 0.01*总数量
    连通域表 = [连通域典[标记] for 标记 in 连通域典 if 连通域典[标记][数量] > 数量阈值]

    print(f'连通域数:{len(连通域表)}')
    return 连通域表, 行数, 列数


def 获取结构信息(字):
    _, 左, 右, 顶, 底 = tuple(range(5))
    码 = 字转码(字)
    连通域表, 行数, 列数 = 获取连通域表(字)
    连通域数 = len(连通域表)
    if 连通域数 == 1:
        结构信息 = [字, 码, '其他', np.nan, np.nan]
        return 结构信息

    水平重叠度图 = nx.Graph()
    水平重叠度图.add_nodes_from(range(连通域数))
    竖直重叠度图 = nx.Graph()
    竖直重叠度图.add_nodes_from(range(连通域数))

    for 甲索引 in range(连通域数):
        for 乙索引 in range(甲索引+1, 连通域数):
            甲 = 连通域表[甲索引]
            乙 = 连通域表[乙索引]
            水平重叠 = max(0, min(甲[右], 乙[右]) - max(甲[左], 乙[左])+1)
            水平重叠度 = 水平重叠*2/(甲[右]-甲[左]+1 + 乙[右]-乙[左]+1)
            if 水平重叠度 > 重叠度阈值:
                水平重叠度图.add_edge(甲索引, 乙索引)

            竖直重叠 = max(0, min(甲[底], 乙[底]) - max(甲[顶], 乙[顶])+1)
            竖直重叠度 = 竖直重叠*2/(甲[底]-甲[顶]+1 + 乙[底]-乙[顶]+1)
            if 竖直重叠度 > 重叠度阈值:
                竖直重叠度图.add_edge(甲索引, 乙索引)

    水平子图表 = [水平重叠度图.subgraph(c).copy()
             for c in nx.connected_components(水平重叠度图)]
    竖直子图表 = [竖直重叠度图.subgraph(c).copy()
             for c in nx.connected_components(竖直重叠度图)]

    print(f'水平子图数:{len(水平子图表)} 竖直子图数:{len(竖直子图表)}')

    可左右切分, 左比例, 右比例 = False, np.nan, np.nan
    if len(水平子图表) == 2:
        子图位置表 = []
        for 水平子图 in 水平子图表:
            子图左 = min([连通域表[索引][左] for 索引 in 水平子图.nodes])
            子图右 = max([连通域表[索引][右] for 索引 in 水平子图.nodes])
            子图顶 = min([连通域表[索引][顶] for 索引 in 水平子图.nodes])
            子图底 = max([连通域表[索引][底] for 索引 in 水平子图.nodes])
            竖直居中 = abs((子图顶+子图底 - 行数)/行数) < 偏离阈值

            子图位置表.append([子图左, 子图右, 子图顶, 子图底, 竖直居中])

        左子图右 = min([子图位置[1] for 子图位置 in 子图位置表])
        右子图左 = max([子图位置[0] for 子图位置 in 子图位置表])
        最左 = min([子图位置[0] for 子图位置 in 子图位置表])
        最右 = max([子图位置[1] for 子图位置 in 子图位置表])
        分界 = (左子图右+右子图左)/2
        左比例 = (分界-最左+1)/(最右-最左+1)
        右比例 = 1-左比例

        if all([子图位置[-1] for 子图位置 in 子图位置表]):
            可左右切分 = True

    可上下切分, 上比例, 下比例 = False, np.nan, np.nan
    if len(竖直子图表) == 2:
        子图位置表 = []
        for 竖直子图 in 竖直子图表:
            子图左 = min([连通域表[索引][左] for 索引 in 竖直子图.nodes])
            子图右 = max([连通域表[索引][右] for 索引 in 竖直子图.nodes])
            子图顶 = min([连通域表[索引][顶] for 索引 in 竖直子图.nodes])
            子图底 = max([连通域表[索引][底] for 索引 in 竖直子图.nodes])
            水平居中 = abs((子图左+子图右-列数)/列数) < 偏离阈值
            子图位置表.append([子图左, 子图右, 子图顶, 子图底, 水平居中])

        上子图底 = min([子图位置[3] for 子图位置 in 子图位置表])
        下子图顶 = max([子图位置[2] for 子图位置 in 子图位置表])
        最左 = min([子图位置[0] for 子图位置 in 子图位置表])
        最右 = max([子图位置[1] for 子图位置 in 子图位置表])
        最顶 = min([子图位置[2] for 子图位置 in 子图位置表])
        最底 = max([子图位置[3] for 子图位置 in 子图位置表])
        分界 = (上子图底+下子图顶)/2
        上比例 = (分界-最顶+1)/(最底-最顶+1)
        下比例 = 1-上比例

        if all([子图位置[-1] for 子图位置 in 子图位置表]):
            可上下切分 = True

    结构信息 = None
    if 可左右切分 and (not 可上下切分):
        结构信息 = [字, 码, '左右', 左比例, 右比例]
    elif (not 可左右切分) and 可上下切分:
        结构信息 = [字, 码, '上下', 上比例, 下比例]
    else:
        结构信息 = [字, 码, '其他', np.nan, np.nan]
    print(f'结构信息:{结构信息}')
    return 结构信息


def connected_components(binary_image, connectivity=4):
    """
    对二值图像进行连通域分割
    :param binary_image: 二值图像，0表示背景，非0表示前景（通常为1或255）
    :param connectivity: 连通性，4或8邻域
    :return: 标记图像（背景为0，连通域从1开始编号），连通域数量
    """
    # 确保输入是二值图像（非0即前景）
    img = np.where(binary_image != 0, 1, 0).astype(np.int32)
    h, w = img.shape

    # 初始化标记矩阵和并查集
    labels = np.zeros((h, w), dtype=np.int32)
    uf = UnionFind()
    next_label = 1

    # 第一遍扫描：分配临时标签并记录等价关系
    for y in range(h):
        for x in range(w):
            if img[y, x] == 0:  # 背景像素跳过
                continue

            # 获取邻域像素的标签（根据连通性）
            neighbors = []
            if connectivity == 4:
                neighbors = [(y-1, x), (y, x-1)]  # 上、左
            elif connectivity == 8:
                neighbors = [(y-1, x-1), (y-1, x), (y-1, x+1),
                             (y, x-1)]  # 左上、上、右上、左

            # 过滤有效邻域位置
            valid_neighbors = [labels[ny, nx] for ny, nx in neighbors
                               if 0 <= ny < h and 0 <= nx < w and labels[ny, nx] != 0]

            # 若没有有效邻域标签，分配新标签
            if not valid_neighbors:
                labels[y, x] = next_label
                uf.add(next_label)
                next_label += 1
            else:
                # 获取最小邻域标签
                min_label = min(valid_neighbors)
                labels[y, x] = min_label

                # 合并等价标签
                for n in valid_neighbors:
                    if n != min_label:
                        uf.union(min_label, n)

    # 第二遍扫描：使用并查集统一标签
    for y in range(h):
        for x in range(w):
            if labels[y, x] != 0:
                labels[y, x] = uf.find(labels[y, x])

    # 重新映射标签为连续整数（1,2,3,...）
    label_map = {}
    new_label = 1
    for y in range(h):
        for x in range(w):
            if labels[y, x] == 0:
                continue
            root = uf.find(labels[y, x])
            if root not in label_map:
                label_map[root] = new_label
                new_label += 1
            labels[y, x] = label_map[root]

    return labels, len(label_map)


class UnionFind:
    """并查集（支持路径压缩）"""

    def __init__(self):
        self.parent = {}
        self.rank = {}

    def add(self, x):
        if x not in self.parent:
            self.parent[x] = x
            self.rank[x] = 0

    def find(self, x):
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])  # 路径压缩
        return self.parent[x]

    def union(self, x, y):
        root_x = self.find(x)
        root_y = self.find(y)
        if root_x == root_y:
            return

        # 按秩合并
        if self.rank[root_x] < self.rank[root_y]:
            self.parent[root_x] = root_y
        elif self.rank[root_x] > self.rank[root_y]:
            self.parent[root_y] = root_x
        else:
            self.parent[root_y] = root_x
            self.rank[root_x] += 1


def 批量获取结构信息():
    最小码 = 0x4e00
    最大码 = 0x9fa5

    for 码 in range(最小码, 最大码+1):  # ['一', '汉', '中', '胃', '蓝', '铁']:
        字 = chr(码)
        print(f'\n字:{字}')
        结构信息 = 获取结构信息(字)
        print(f'结构信息:{结构信息[1]} {结构信息[2]} {结构信息[3]:.3f} {结构信息[4]:.3f}')


if __name__ == '__main__':
    批量获取结构信息()
